//@author A0105740R
package rightnow.logic;

import java.io.IOException;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.eaio.uuid.UUID;

import rightnow.parser.cmdline.CmdParserBucket;
import rightnow.parser.cmdline.CmdParserException;
import rightnow.parser.cmdline.CommandParser;
import rightnow.storage.Storage;
import rightnow.storage.StorageImpl;
import rightnow.storage.Task;
import rightnow.util.DateTimeFormatters;
import rightnow.util.UiMsg;

/**
 * The concrete class for main logic.
 */
public class LogicImpt implements Logic {
	private static final Logger log = LoggerFactory.getLogger(LogicImpt.class);
	
	private static final String DEBUG_CHANGE_VIEW_RETRIVE_TASKS =
			"Change view, and retrive %s tasks from the storage.";
	private static final String DEBUG_CURRENT_VIEW_RETRIEVE_TASKS =
			"Current view, and retrieve %s tasks from the storage";
	private static final String DEBUG_COMMAND_PARSE_FAILURE = 
			"Command parse failure.";
	private static final String DEBUG_COMMAND_PARSE_SUCCESS = 
			"Command parsed successfully.";

	private static final int INDEX_NOT_FOUND = -1;

	private static final int VIEW_INDEX_ADJUSTMENT = -1;
	
	private Storage storage;
	private CommandParser cmdParser = new CommandParser();
	private ViewList viewList;
	private FeedbackProvider feedbackProvider = FeedbackProvider.getInstance();
	
	@Override
	public void initStorage() throws IOException {
		if (storage == null) {
			this.storage = new StorageImpl();
			
			try {
				storage.loadFile();
			}
			catch (IOException e) {}
			
			// this statement may throw IOException
			storage.saveFile();
			
			this.viewList = new ViewList(storage.allTasksToList(), 
					ViewListStatus.ALL, SortingMethod.DEFAULT);
			updateListView();
		}
	}
	
	/**
	 * Debug use only.
	 * @param storage
	 */
	public void setStorage(Storage storage) {
		this.storage = storage;
		this.viewList = new ViewList(storage.allTasksToList(),
				ViewListStatus.ALL, SortingMethod.DEFAULT);
	}

	@Override
	public void executeCommand(String commandString) throws IOException {
		CmdParserBucket bucket = new CmdParserBucket();
		try {
			cmdParser.parseInto(bucket, commandString);
		} catch (CmdParserException e) {
			log.debug(DEBUG_COMMAND_PARSE_FAILURE);
			feedbackProvider.setMsg(e.getMessage());
			return ;
		}
		log.debug(DEBUG_COMMAND_PARSE_SUCCESS);
		
		switch (bucket.getCmdType()) {
		case ADD:
			addTask(bucket.getTask());
			break;
		case DELETE:
			deleteTask(bucket.getInt());
			break;
		case DELETE_DONE:
			deleteDoneTasks();
			break;
		case EDIT:
			editTask(bucket.getInt(), bucket.getTask());
			break;
		case EDIT_INIT:
			editTaskInit(bucket.getInt());
			break;
		case SEARCH:
			search(bucket.getString());
			break;
		case ALL:
			listAllTasks();
			break;
		case NOW:
			listNowTasks();
			break;
		case DONE:
			doneTask(bucket.getInt());
			break;
		case UNDONE:
			undoneTask(bucket.getInt());
			break;
		case PIN:
			pinTask(bucket.getInt());
			break;
		case UNPIN:
			unpinTask(bucket.getInt());
			break;
		case REDO:
			redo();
			break;
		case UNDO:
			undo();
			break;
		case NEXT:
			nextPage();
			break;
		case PREV:
			prevPage();
			break;
		case PAGE:
			turnToPage(bucket.getInt());
			break;
		default:
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_FUNCTION_UNDER_CONSTRUCTION);
			log.warn("Command not supported.");
		}
	}
	
	protected void addTask(Task newTask) throws IOException {
		UUID taskId;
		taskId = storage.addTask(newTask);
		
		// this statement may throw IOException
		storage.saveFile();

		// update list
		if (viewList.getStatus() == ViewListStatus.SEARCH) {
			updateListContentFromStorage(ViewListStatus.ALL);
		}
		else {
			updateListContentFromStorage();
		}
		updateListView(taskId);
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_ADD_SUCCESS);
	}
	
	protected void deleteTask(int index) throws IOException {
		UUID taskId;
		try {
			taskId = viewList.getTask(index).getTaskId();
		}
		catch (IndexOutOfBoundsException e) {
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_INVALID_INDEX);
			return ;
		}
		
		storage.deleteTask(taskId);
		
		// this statement may throw IOException
		storage.saveFile();
		
		// update list
		updateListContentFromStorage();
		updateListView();
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_DEL_SUCCESS);
	}

	protected void deleteDoneTasks() throws IOException {
		storage.deleteDoneTasks();
		
		// this statement may throw IOException
		storage.saveFile();
		
		// update list
		updateListContentFromStorage();
		updateListView();
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_DEL_DONE_SUCCESS);
	}

	protected void editTaskInit(int viewIndex) {
		Task task; 
		try {
			task = viewList.getTask(viewIndex);
		}
		catch (IndexOutOfBoundsException e) {
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_INVALID_INDEX);
			return ;
		}
		
		// update inputBoxString
		feedbackProvider.setCmdLine("edit " 
				+ viewIndex + generateTaskInputString(task));
		// update list
		updateListView(viewIndex + VIEW_INDEX_ADJUSTMENT);
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_EMPTY);
	}

	protected void editTask(int index, Task sourceTask) throws IOException {
		UUID taskId;
		try {
			taskId = viewList.getTask(index).getTaskId();
		}
		catch (IndexOutOfBoundsException e) {
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_INVALID_INDEX);
			return ;
		}
		
		storage.editTask(taskId, sourceTask);
		
		// this statement may throw IOException
		storage.saveFile();
		
		// update list
		if (viewList.getStatus() == ViewListStatus.SEARCH) {
			updateListContentFromStorage(ViewListStatus.ALL);
		}
		else {
			updateListContentFromStorage();
		}
		updateListView(taskId);
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_EDIT_SUCCESS);
	}
	
	protected void doneTask(int index) throws IOException {
		UUID taskId;
		try {
			taskId = viewList.getTask(index).getTaskId();
		}
		catch (IndexOutOfBoundsException e) {
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_INVALID_INDEX);
			return ;
		}
		
		storage.doneTask(taskId);
		
		// this statement may throw IOException
		storage.saveFile();	
		
		// update list
		updateListContentFromStorage();
		updateListView();
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_TASK_DONE);
	}

	protected void undoneTask(int index) throws IOException {
		UUID taskId;
		try {
			taskId = viewList.getTask(index).getTaskId();
		}
		catch (IndexOutOfBoundsException e) {
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_INVALID_INDEX);
			return ;
		}
		
		storage.undoneTask(taskId);
		
		// this statement may throw IOException
		storage.saveFile();
		
		// update list
		updateListContentFromStorage();
		updateListView();
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_TASK_UNDONE);
	}
	
	protected void pinTask(int index) throws IOException {
		UUID taskId;
		try {
			taskId = viewList.getTask(index).getTaskId();
		}
		catch (IndexOutOfBoundsException e) {
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_INVALID_INDEX);
			return ;
		}
		
		storage.pinTask(taskId);
		
		// this statement may throw IOException
		storage.saveFile();	
		
		// update list
		updateListContentFromStorage();
		updateListView(taskId);
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_TASK_PINNED);
	}
	
	protected void unpinTask(int index) throws IOException {
		UUID taskId;
		try {
			taskId = viewList.getTask(index).getTaskId();
		}
		catch (IndexOutOfBoundsException e) {
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_INVALID_INDEX);
			return ;
		}
		
		storage.unpinTask(taskId);
		
		// this statement may throw IOException
		storage.saveFile();	
		
		// update list
		updateListContentFromStorage();
		updateListView(taskId);
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_TASK_UNPINNED);
	}
	
	protected void undo() throws IOException {
		String msg = String.format(UiMsg.MSG_UNDO_SUCCESS, 
				storage.getStateChange());
		if (!storage.undo()) {
			feedbackProvider.setMsg(UiMsg.MSG_CANNOT_UNDO);
			return ;
		}
		
		// this statement may throw IOException
		storage.saveFile();
		
		// update list
		if (viewList.getStatus() == ViewListStatus.SEARCH) {
			updateListContentFromStorage(ViewListStatus.ALL);
		}
		else {
			updateListContentFromStorage();
		}
		updateListView();
		// update message
		feedbackProvider.setMsg(msg);
	}

	protected void redo() throws IOException {
		String msg;
		if (!storage.redo()) {
			feedbackProvider.setMsg(UiMsg.MSG_CANNOT_REDO);
			return ;
		}
		
		// this statement may throw IOException
		storage.saveFile();	
		
		// update list
		if (viewList.getStatus() == ViewListStatus.SEARCH) {
			updateListContentFromStorage(ViewListStatus.ALL);
		}
		else {
			updateListContentFromStorage();
		}
		updateListView();
		// update message
		msg = String.format(UiMsg.MSG_REDO_SUCCESS, 
				storage.getStateChange());
		feedbackProvider.setMsg(msg);
	}

	protected void search(String string) {
		viewList.setSearchTerm(string);
		
		// update list
		updateListContentFromStorage(ViewListStatus.SEARCH);
		updateListView();
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_EMPTY);
	}

	protected void listAllTasks() {
		// update list
		updateListContentFromStorage(ViewListStatus.ALL);
		updateListView();
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_EMPTY);
	}
	
	protected void listNowTasks() {
		// update list
		updateListContentFromStorage(ViewListStatus.NOW);
		updateListView();
		// update message
		feedbackProvider.setMsg(UiMsg.MSG_EMPTY);
	}
	
	protected void nextPage() {
		if (viewList.turnToPage(viewList.getCurrentPageNum() + 1)) {
			// update list
			updateListView();
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_EMPTY);
		}
		else {
			feedbackProvider.setMsg(UiMsg.MSG_NO_NEXT_PAGE);
		}
	}
	
	protected void prevPage() {
		if (viewList.turnToPage(viewList.getCurrentPageNum() - 1)) {
			// update list
			updateListView();
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_EMPTY);
		}
		else {
			feedbackProvider.setMsg(UiMsg.MSG_NO_PREV_PAGE);
		}
	}

	protected void turnToPage(int pageNum) {
		if (viewList.turnToPage(pageNum)) {
			// update list
			updateListView();
			// update message
			feedbackProvider.setMsg(UiMsg.MSG_EMPTY);
		}
		else {
			feedbackProvider.setMsg(UiMsg.MSG_INVALID_PAGE_NUM);
		}
	}
	
	private void updateListContentFromStorage() {
		switch (viewList.getStatus()) {
		case ALL:
			viewList.setCurrentStatusList(storage.allTasksToList());
			break;
		case NOW:
			viewList.setCurrentStatusList(
					storage.nowTasksToList(new DateTime()));
			break;
		case SEARCH:
			viewList.setCurrentStatusList(
					storage.matchedTasksToList(viewList.getSearchTerm()));
			break;
		default:
			assert false;
		}
		log.debug(String.format(DEBUG_CURRENT_VIEW_RETRIEVE_TASKS,
				getViewStatusMessage()));
	}
	
	private void updateListContentFromStorage(ViewListStatus status) {
		switch (status) {
		case ALL:
			viewList.setNewStatusList(storage.allTasksToList(), status);
			break;
		case NOW:
			viewList.setNewStatusList(
					storage.nowTasksToList(new DateTime()), status);
			break;
		case SEARCH:
			viewList.setNewStatusList(
					storage.matchedTasksToList(
							viewList.getSearchTerm()), status);
			break;
		default:
			assert false;
		}
		log.debug(String.format(DEBUG_CHANGE_VIEW_RETRIVE_TASKS,
				getViewStatusMessage()));
	}

	private String getViewStatusMessage() {
		switch (viewList.getStatus()) {
		case ALL:
			return UiMsg.MSG_VIEW_STATUS_ALL;
		case NOW:
			return UiMsg.MSG_VIEW_STATUS_NOW;
		case SEARCH:
			return UiMsg.generateSearchViewStatusMsg(viewList.getSearchTerm());
		default:
			assert false;
			return null;
		}
	}
	
	private void updateListView() {
		updateListView(INDEX_NOT_FOUND);
	}

	private void updateListView(UUID taskId) {
		int viewIndexToHighlight = viewList.highlightTask(taskId);
		updateListView(viewIndexToHighlight);
	}

	private void updateListView(int viewIndexToHighlight) {
		feedbackProvider.setList(viewList.getListCurrentPage(), 
				viewList.getNumTotalTasks(),
				viewList.getCurrentPageNum(), viewList.getNumTotalPages(), 
				getViewStatusMessage(), viewIndexToHighlight);
	}
	
	private String generateTaskInputString(Task task) {
		StringBuilder sbd = new StringBuilder();
		sbd.append(" \" " + task.getDescription() + " \"");
		if (task.getStartTime() != null) {
			sbd.append(" at ");
			sbd.append(DateTimeFormatters.
					time24Formatter.print(task.getStartTime()));
			sbd.append(" on ");
			sbd.append(DateTimeFormatters.
					stdDateFormatter.print(task.getStartDate()));
		}
		if (task.getEndTime() != null) {
			sbd.append(" by ");
			sbd.append(DateTimeFormatters.
					time24Formatter.print(task.getEndTime()));
			sbd.append(" on ");
			sbd.append(DateTimeFormatters.
					stdDateFormatter.print(task.getEndDate()));
		}
		return sbd.toString();
	}
}
